package com.flyqiu.flow.core.handler;

import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.tools.FlyQiuCollectionTools;
import com.flyqiu.common.user.FlyQiuUser;
import com.flyqiu.common.user.FlyQiuUserTools;
import com.flyqiu.flow.api.context.ProcInstContext;
import com.flyqiu.flow.api.script.IVariableScope;
import com.flyqiu.flow.api.service.ru.FlowRuVariableService;
import com.flyqiu.flow.api.std.ElementStartHandler;
import com.flyqiu.flow.bpmn.FlyQiuBpmModeler;
import com.flyqiu.flow.bpmn.element.ProcessModel;
import com.flyqiu.flow.bpmn.element.StartEvent;
import com.flyqiu.flow.common.enums.ProcInstStatus;
import com.flyqiu.flow.common.enums.VarScopeType;
import com.flyqiu.flow.core.DefaultProcInstContext;
import com.flyqiu.flow.core.tools.EventTools;
import com.flyqiu.flow.event.ProcInstEvent;
import com.flyqiu.flow.event.ProcInstEventContext;
import com.flyqiu.flow.event.api.FlyQiuFlowEventManage;
import com.flyqiu.flow.mapper.HiProcInstMapper;
import com.flyqiu.flow.mapper.RuProcInstMapper;
import com.flyqiu.flow.model.business.BaseFlyBusiness;
import com.flyqiu.flow.model.entity.ProcDeployDef;
import com.flyqiu.flow.model.entity.base.Variable;
import com.flyqiu.flow.model.entity.hi.ProcHiInst;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;
import com.flyqiu.flow.model.entity.ru.ProcRuVariable;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public class ProcessHandler implements ElementStartHandler<ProcRuInst> {
    private ProcInstEventDispatch dispatch = new ProcInstEventDispatch();
    private FlyQiuFlowEventManage procEventManage;

    @Override
    public ProcRuInst start(ProcInstContext procContext, String currentDefKey) {
        procEventManage = procContext.getGlobalContext().getFlowEventManage();

        FlyQiuBpmModeler flyQiuBpmModeler = procContext.getBpmModeler();
        List<ProcessModel> processModels = flyQiuBpmModeler.getProcessModels();
        Optional<ProcessModel> first = processModels.stream().filter(e -> e != null && e.getDefKey().equals(currentDefKey)).findFirst();
        if (first.isEmpty()) {
            throw new RuntimeException("Process definition with procDeployDefKey:" + currentDefKey + " not found");
        }
        ProcessModel processModel = first.get();
        StartEvent startEvent = processModel.getStartEvent();
        if (startEvent == null) {
            throw new RuntimeException("Process StartEvent definition with procDeployDefKey:" + currentDefKey + " not found");
        }
        ProcRuInst procInst = createProcInst(procContext);


        FlyQiuBeanManage procBean = procContext.getGlobalContext().getBeanManage();
        RuProcInstMapper ruProcInstMapper = procBean.getMapper(RuProcInstMapper.class);
        HiProcInstMapper historyInstMapper = procBean.getMapper(HiProcInstMapper.class);

        ruProcInstMapper.insert(procInst);
        saveProcVar(procContext, procInst);
        ProcInstEventContext eventContext = EventTools.createContext(procContext, procInst);
        dispatch.createBefore(eventContext);
        ProcHiInst procHiInst = ProcHiInst.create(procInst);
        historyInstMapper.insert(procHiInst);
        DefaultProcInstContext defaultProcInstContext = createProcContext(procContext);

        defaultProcInstContext.setProcInst(procInst);
        StartEventHandler startEventHandler = new StartEventHandler();
        startEventHandler.start(defaultProcInstContext, startEvent.getDefKey());
        dispatch.createAfter(eventContext);
        return procInst;
    }


    /**
     * 创建流程实例上下文
     *
     * @param procContext 流程实例上下文
     * @return DefaultProcInstContext
     */
    private DefaultProcInstContext createProcContext(ProcInstContext procContext) {
        if (procContext instanceof DefaultProcInstContext) {
            return (DefaultProcInstContext) procContext;
        }
        return DefaultProcInstContext.create(procContext);
    }

    private ProcRuInst createProcInst(ProcInstContext procInstContext) {
        ProcDeployDef procDeployDef = procInstContext.getProcDeployDef();
        ProcRuInst procInst = new ProcRuInst();
        //设置流程基本信息
        procInst.setProcInstName(procDeployDef.getProcDeployName());
        procInst.setProcDeployDefKey(procDeployDef.getProcDeployDefKey());
        procInst.setProcDeployDefId(procDeployDef.getId());
        procInst.setParentLevelDefKeys("");
        procInst.setParentProcDefKey("");
        procInst.setParentId("");
        procInst.setStatus(ProcInstStatus.ACTIVATE.getValue());
        procInst.setCreateTime(LocalDateTime.now());

        //设置流程业务信息
        BaseFlyBusiness baseFlyBusiness = procInstContext.getProcBusiness();
        procInst.setBusinessKey(baseFlyBusiness.getBusinessKey());
        procInst.setBusinessId(baseFlyBusiness.getBusinessId());
        procInst.setBusinessParams(baseFlyBusiness.getBusinessParams());

        //设置流程发起人信息
        FlyQiuUser flyQiuFlowAuthentication = FlyQiuUserTools.getAuthentication();
        procInst.setTenantId(flyQiuFlowAuthentication.getTenantId());
        procInst.setStartUserId(flyQiuFlowAuthentication.getId());
        procInst.setStartUserKey(flyQiuFlowAuthentication.getUserType());
        procInst.setStartUserNickName(flyQiuFlowAuthentication.getNickName());
        procInst.setStartUserAttr(flyQiuFlowAuthentication.getUserAttr());
        procInst.setStartUserUniqueKey(flyQiuFlowAuthentication.getUniqueKey());

        return procInst;
    }

    /**
     * 保存流程变量
     *
     * @param procInstContext 流程实例上下文
     * @param procInst        当前流程实例
     */
    private void saveProcVar(ProcInstContext procInstContext, ProcRuInst procInst) {
        IVariableScope procVariables = procInstContext.getProcVariables();
        if (procVariables == null) return;
        List<Variable> variables = procVariables.getVariables();
        if (FlyQiuCollectionTools.isEmpty(variables)) {
            return;
        }
        FlyQiuBeanManage procBean = procInstContext.getGlobalContext().getBeanManage();
        FlowRuVariableService variableService = procBean.getService(FlowRuVariableService.class);
        List<ProcRuVariable> procRuVariableList = variables.stream().filter(e -> e instanceof ProcRuVariable).map(e -> {
            ProcRuVariable variable = (ProcRuVariable) e;
            variable.setProcInstId(procInst.getId());
            variable.setTenantId(procInst.getTenantId());
            variable.setVarScope(VarScopeType.PROCESS.getValue());
            variable.setCreateTime(LocalDateTime.now());
            return variable;
        }).toList();
        variableService.saveBatch(procRuVariableList);
    }

    private class ProcInstEventDispatch implements ProcInstEvent {


        @Override
        public void createBefore(ProcInstEventContext context) {
            procEventManage.triggerCreateBeforeEvent(ProcInstEvent.class, context);
        }

        @Override
        public void createAfter(ProcInstEventContext context) {
            procEventManage.triggerCreateAfterEvent(ProcInstEvent.class, context);
        }

    }

}
